﻿using EmailClient.Model;
using EmailClient.ViewModel;
using LumiSoft.Net;
using LumiSoft.Net.IMAP;
using LumiSoft.Net.IMAP.Client;
using LumiSoft.Net.Mail;
using MarkupConverter;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Forms;

namespace EmailClient.Mail
{
    public class IMAP
    {
        public IMAP_Client client = null;
        public List<long> listUID = new List<long>();
        public int folderId = 0;
        public EmailAccount account = null;
        public ObservableCollection<Letter> letters = new ObservableCollection<Letter>();
        public Letter currLetter = null;
        private IMarkupConverter markupConverter;

        public static string CheckData(EmailAccount account)
        {
            var result = "success";
            IMAP_Client imap = new IMAP_Client();
            try
            {
                imap.Connect(account.inServer, account.inPort, account.UseSSL);
                imap.Login(account.UserName, account.Password);
                imap.Disconnect();
                imap.Dispose();
            }
            catch (Exception e)
            {
                result = e.Message;
            }
            return result;
        }

        public static IMAP_Client CreateConnect(EmailAccount account)
        {
            IMAP_Client imap = new IMAP_Client();
            try
            {
                imap.Connect(account.inServer, account.inPort, account.UseSSL);
                imap.Login(account.UserName, account.Password);
            }
            catch
            {
                imap.Dispose();
            }
            return imap;
        }

        public static async void LoadFolders(EmailAccount account)
        {
            try
            {
                var client = CreateConnect(account);
                IMAP_r_u_List[] folders = client.GetFolders(null);

                char folderSeparator = client.FolderSeparator;
                foreach (IMAP_r_u_List folder in folders)
                {
                    string[] folderPath = folder.FolderName.Split(folderSeparator);

                    // Conatins sub folders.
                    if (folderPath.Length > 1)
                    {
                        var parentName = folderPath[folderPath.Length - 2].ToString().Trim();
                        int parentId = (await MailFolderViewModel.GetFolder(account.Id, parentName)).Id;
                        foreach (string fold in folderPath)
                        {
                            if (fold != parentName) await MailFolderViewModel.InsertUpdate(new MailFolder { Name = fold, AccountId = account.Id, ParentId = parentId });
                        }
                    }
                    else
                    {
                        await MailFolderViewModel.InsertUpdate(new MailFolder { Name = folder.FolderName, AccountId = account.Id });
                    }
                }
            }
            catch (Exception x)
            {
                MessageBox.Show("Error:" + x.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public static async Task<string> ClearFolder(EmailAccount account, int folderId, ItemCollection letters)
        {
            var result = "success";
            var curFolder = await MailFolderViewModel.GetFolder(account.Id, folderId);
            var path = await IMAP.GetPathFolder(curFolder);

            long[] seq = new long[letters.Count];
            for (int i = 0; i < letters.Count; i++)
            {
                var letter = letters[i] as Letter;
                seq[i] = letter.UID;
            }

            try
            {
                using (var client = IMAP.CreateConnect(account))
                {
                    client.SelectFolder(path);
                    client.StoreMessageFlags(true, IMAP_t_SeqSet.Parse(String.Join(",", seq)), IMAP_Flags_SetType.Add, IMAP_t_MsgFlags.Parse(IMAP_t_MsgFlags.Deleted));
                    client.Expunge();
                    client.CloseFolder();
                }
            }
            catch (Exception e) { result = e.Message; }

            return result;
        }

        public static async Task<string> DeleteLetters(EmailAccount account, int folderId, long[] seq)
        {
            var result = "success";
            var curFolder = await MailFolderViewModel.GetFolder(account.Id, folderId);
            var path = await IMAP.GetPathFolder(curFolder);

            try
            {
                using (var client = IMAP.CreateConnect(account))
                {
                    client.SelectFolder(path);
                    client.StoreMessageFlags(true, IMAP_t_SeqSet.Parse(String.Join(",", seq)), IMAP_Flags_SetType.Add, IMAP_t_MsgFlags.Parse(IMAP_t_MsgFlags.Deleted));
                    client.Expunge();
                    client.CloseFolder();
                }
            }
            catch (Exception e) { result = e.Message; }

            return result;
        }

        public static async Task<ObservableCollection<Letter>> Searching(EmailAccount account, int folderId, string searchString)
        {
            var curFolder = await MailFolderViewModel.GetFolder(account.Id, folderId);
            var path = await IMAP.GetPathFolder(curFolder);
            List<int> uids = new List<int>();
            var imapClient = new IMAP();
            imapClient.client = IMAP.CreateConnect(account);
            imapClient.account = account;

            try
            {
                imapClient.client.SelectFolder(path);
                var keys = new List<IMAP_Search_Key>();
                keys.Add(new IMAP_Search_Key_From(searchString));
                keys.Add(new IMAP_Search_Key_To(searchString));
                keys.Add(new IMAP_Search_Key_Cc(searchString));
                keys.Add(new IMAP_Search_Key_Bcc(searchString));
                keys.Add(new IMAP_Search_Key_Subject(searchString));
                keys.Add(new IMAP_Search_Key_Body(searchString));
                SearchLetterUseImap(imapClient.client, keys, ref uids);
            }
            catch (Exception e)
            {
                //LogViewModel.InsertUpdate(new Log() { Event = "Searching IMAP: " + e.Message, DateEvent = DateTime.Now, AccountId = account.Id });
            }
            

            if (uids.Count > 0)
            {
                var letters = new ObservableCollection<Letter>();
                foreach (long uid in uids)
                {
                    imapClient.LoadMessage(uid, path);
                    if (imapClient.currLetter != null) letters.Add(imapClient.currLetter); 
                }
                return letters;
            } 
            return null;
        }

        private static void SearchLetterUseImap(IMAP_Client client, List<IMAP_Search_Key> keys, ref List<int> uids)
        {
            foreach (var key in keys)
            {
                var res = client.Search(true, Encoding.UTF8, key);
                for (int i = 0; i < res.Count(); i++)
                {
                    int uid = res[i];
                    var query = uids.Where(x => x == uid).FirstOrDefault();
                    if (query == 0) uids.Add(uid);
                }
            }
        }

        /// <summary>
        /// This method is called when FETCH (Envelope Flags InternalDate Rfc822Size Uid) untagged response is received.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private async void m_pImap_Fetch_MessageItems_UntaggedResponse(object sender, EventArgs<IMAP_r_u> e)
        {
            /* NOTE: All IMAP untagged responses may be raised from thread pool thread,
                so all UI operations must use Invoke.
             
               There may be other untagged responses than FETCH, because IMAP server
               may send any untagged response to any command.
            */
            var result = "success";
            try
            {
                if (e.Value is IMAP_r_u_Fetch)
                {
                    IMAP_r_u_Fetch fetchResp = (IMAP_r_u_Fetch)e.Value;
                    await ProcessResponse(fetchResp);
                    //await LogViewModel.InsertUpdate(new Log(){Event = await ProcessResponse(fetchResp), DateEvent = DateTime.Now, AccountId = this.account.Id});
                }
            }
            catch (Exception x)
            {
                result = x.ToString();
            }
            if (result != "success") await LogViewModel.InsertUpdate(new Log() { Event = result, DateEvent = DateTime.Now, AccountId = this.account.Id });
        }

        private Task<string> ProcessResponse(IMAP_r_u_Fetch fetchResp)
        {
            return Task.Factory.StartNew(() =>
           {
               var result = "success";
               try
               {
                   var newLetter = new Letter();
                   newLetter.UID = fetchResp.UID.UID;

                   string from = "";
                   if (fetchResp.Envelope.From != null)
                   {
                       for (int i = 0; i < fetchResp.Envelope.From.Length; i++)
                       {
                           // Don't add ; for last item
                           if (i == fetchResp.Envelope.From.Length - 1)
                           {
                               from += fetchResp.Envelope.From[i].ToString();
                           }
                           else
                           {
                               from += fetchResp.Envelope.From[i].ToString() + ";";
                           }
                       }
                   }
                   else
                   {
                       from = "<none>";
                   }

                   string to = "";
                   if (fetchResp.Envelope.To != null)
                   {
                       for (int i = 0; i < fetchResp.Envelope.To.Length; i++)
                       {
                           // Don't add ; for last item
                           if (i == fetchResp.Envelope.To.Length - 1)
                           {
                               to += fetchResp.Envelope.To[i].ToString();
                           }
                           else
                           {
                               to += fetchResp.Envelope.To[i].ToString() + ";";
                           }
                       }
                   }
                   else
                   {
                       from = "<none>";
                   }

                   newLetter.FromSent = from;
                   newLetter.ToSent = to;

                   newLetter.Subject = (fetchResp.Envelope.Subject != null ? fetchResp.Envelope.Subject : "<none>");

                   newLetter.DepartureDate = (fetchResp.InternalDate.Date);
                   newLetter.Account = this.account;
                   if (fetchResp.Flags.Flags.ToString().Contains("Seen")) newLetter.Read = true;
                   this.letters.Add(newLetter);
               }
               catch (Exception x)
               {

                   result = x.ToString();
               }
               return result;
           });
        }

        /// <summary>
        /// Gets specified folder messages list from IMAP server and adds them to UI.
        /// </summary>
        /// <param name="folder">IMAP folder which messages to load.</param>
        public Task<string> LoadFolderMessages(string folder)
        {
            return Task.Factory.StartNew(() =>
           {
               var result = "success";
               try
               {
                   client.SelectFolder(folder);

                   // No messages in folder, skip fetch.
                   if (client.SelectedFolder.MessagesCount == 0)
                   {
                       return result;
                   }

                   // Start fetching.
                   client.Fetch(
                       false,
                       IMAP_t_SeqSet.Parse("1:*"),
                       new IMAP_t_Fetch_i[]{
                        new IMAP_t_Fetch_i_Envelope(),
                        new IMAP_t_Fetch_i_Flags(),
                        new IMAP_t_Fetch_i_InternalDate(),
                        new IMAP_t_Fetch_i_Rfc822Size(),
                        new IMAP_t_Fetch_i_Uid()
                    },
                       this.m_pImap_Fetch_MessageItems_UntaggedResponse
                   );
               }
               catch (Exception x)
               {
                   result = x.Message;
               }
               return result;
           });
        }

        /// <summary>
        /// This method is called when FETCH RFC822 untagged response is received.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pImap_Fetch_Message_UntaggedResponse(object sender, EventArgs<IMAP_r_u> e)
        {
            /* NOTE: All IMAP untagged responses may be raised from thread pool thread,
                so all UI operations must use Invoke.
             
               There may be other untagged responses than FETCH, because IMAP server
               may send any untagged response to any command.
            */
            var result = "success";
            try
            {
                if (e.Value is IMAP_r_u_Fetch)
                {
                    IMAP_r_u_Fetch fetchResp = (IMAP_r_u_Fetch)e.Value;
                    try
                    {
                        fetchResp.Rfc822.Stream.Position = 0;
                        Mail_Message mime = Mail_Message.ParseFromStream(fetchResp.Rfc822.Stream);
                        fetchResp.Rfc822.Stream.Dispose();

                        currLetter = new Letter();
                        currLetter.UID = fetchResp.UID.UID;
                        currLetter.Bcc = (mime.Bcc == null ? null : mime.Bcc.ToString());
                        currLetter.BodyAsText = mime.BodyText;
                        currLetter.BodyAsHTML = mime.BodyText;
                        currLetter.BodyAsHTML = mime.BodyHtmlText;
                        currLetter.CC = (mime.Cc == null ? null : mime.Cc.ToString());
                        currLetter.DepartureDate = (DateTime)mime.Date;
                        currLetter.FromSent = mime.From.ToString();
                        currLetter.MessageId = mime.MessageID;
                        currLetter.Subject = mime.Subject;
                        currLetter.ToSent = mime.To.ToString();
                        currLetter.AccountId = account.Id;
                        currLetter.Account = account;

                        var listAttach = new List<Attachment>();
                        foreach (var attachment in mime.Attachments)
                        {
                            var newAttach = new Attachment();
                            if (attachment.ContentDisposition != null && attachment.ContentDisposition.Param_FileName != null)
                            {
                                newAttach.Name = attachment.ContentDisposition.Param_FileName;
                            }
                            else
                            {
                                newAttach.Name = "untitled";
                            }
                            newAttach.attach = attachment.Body.Entity.DataToByte();
                            newAttach.ContentTransferEncoding = attachment.ContentTransferEncoding.ToString();
                            newAttach.ContentType = attachment.ContentType.ToString();
                            listAttach.Add(newAttach);
                        }
                        currLetter.Attachments = listAttach;
                    }
                    catch (Exception x)
                    {
                        result = x.Message;
                    }
                }
            }
            catch (Exception x)
            {
                result = x.Message;
            }
            //if (result != "success") LogViewModel.InsertUpdate(new Log() { Event = result, DateEvent = DateTime.Now, AccountId = this.account.Id });
        }


        /// <summary>
        /// Load specified IMAP message to UI.
        /// </summary>
        /// <param name="uid">Message IMAP UID value.</param>
        public string LoadMessage(long uid, string path)
        {
            var result = "success";
            try
            {
                client.SelectFolder(path);
                // Start fetching.
                client.Fetch(
                    true,
                    IMAP_t_SeqSet.Parse(uid.ToString()),
                    new IMAP_t_Fetch_i[]{
                        new IMAP_t_Fetch_i_Rfc822()
                    },
                    this.m_pImap_Fetch_Message_UntaggedResponse
                );
            }
            catch (Exception x)
            {
                result = x.Message;
            }
            return result;

        }

        public static async Task<string> GetPathFolder(MailFolder folder, string path = "")
        {

            if (folder.ParentId == 0) path = folder.Name;
            else
            {
                var parent = await MailFolderViewModel.GetFolder(folder.AccountId, folder.ParentId);
                path += (path.Length == 0 ? parent.Name + "/" + folder.Name : "/" + parent.Name + "/" + folder.Name);
                if (parent.ParentId == 0) await GetPathFolder(parent, path);
            }
            return path;
        }
    }
}
